﻿#region [ License information          ]
/* ************************************************************
 *
 * Copyright (c) Bjørn Bouet Smith, 2010, 2011
 *
 * This source code is subject to terms and conditions of 
 * Microsoft Public License (Ms-PL).
 * 
 * A copy of the license can be found in the license.txt
 * file at the root of this distribution. If you can not 
 * locate the License, please send an email to bjornsmith@gmail.com
 * 
 * By using this source code in any fashion, you are 
 * agreeing to be bound by the terms of the Microsoft 
 * Permissive License.
 *
 * You must not remove this notice, or any other, from this
 * software.
 *
 * ************************************************************/
#endregion

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using Smith.AMC.Configuration;

namespace Smith.AMC.Network
{
    /// <summary>
    /// Represents the pool of sockets and servers
    /// </summary>
    public class ServerPool : IDisposable
    {
        /// <summary>
        /// The server monitor that will monitor all server nodes.
        /// </summary>
        private readonly ServerNodeMonitor serverNodeMonitor;

        /// <summary>
        /// The list of servers that participate in the memcached server cluster
        /// </summary>
        private IList<KeyValuePair<IPAddress, int>> targetServers;
        
        /// <summary>
        /// The socket type to use for sockets, i.e. udp or tcp.
        /// </summary>
        private SocketType socketType;
        
        /// <summary>
        /// The protocol type top use for the clients.
        /// </summary>
        private ProtocolType protocol;

        /// <summary>
        /// The maximum number of socket to open to each server
        /// </summary>
        private int maxSocketsPerServer;

        /// <summary>
        /// The list of memcached server nodes.
        /// </summary>
        private List<IMemcachedServerNode> servers;

        /// <summary>
        /// The server locator that helps with locating the correct server node for a given cache key.
        /// </summary>
        private ServerLocator serverLocator;

        /// <summary>
        /// Initializes a new instance of the <see cref="ServerPool"/> class.
        /// </summary>
        /// <param name="targetServers">The target servers.</param>
        /// <param name="socketType">Type of the socket.</param>
        /// <param name="protocol">The protocol.</param>
        /// <param name="maxSocketsPerServer">The max sockets per server.</param>
        /// <param name="monitorConfiguration">The configuration for the server node monitor.</param>
        public ServerPool(
            IList<KeyValuePair<IPAddress, int>> targetServers, 
            SocketType socketType,
            ProtocolType protocol, 
            int maxSocketsPerServer,
            ServerNodeMonitorConfiguration monitorConfiguration)
        {
            this.targetServers = targetServers;
            this.socketType = socketType;
            this.protocol = protocol;
            this.maxSocketsPerServer = maxSocketsPerServer;
            
            InitPool();
            serverNodeMonitor = new ServerNodeMonitor(servers, monitorConfiguration);
            serverNodeMonitor.Start();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ServerPool"/> class.
        /// </summary>
        /// <param name="configuration">The configuration to configure the pool with.</param>
        public ServerPool(MemcachedClientConfiguration configuration)
        {
            ConfigurePool(configuration);
            InitPool();
        }

        /// <summary>
        /// Aquires a client socket from the server pool for the given cache key
        /// </summary>
        /// <param name="timeout">The timeout.</param>
        /// <param name="cacheKey">The cache key.</param>
        /// <returns>A client socket ready for use</returns>
        public IClientSocket AquireSocket(TimeSpan timeout, string cacheKey)
        {
            IMemcachedServerNode serverNode = serverLocator.LocateServer(cacheKey);

            return serverNode.AquireSocket(timeout);
        }

        /// <summary>
        /// Releases the socket into the server pool again
        /// </summary>
        /// <param name="socket">The socket to release.</param>
        public void ReleaseSocket(IClientSocket socket)
        {
            if (socket == null)
            {
                throw new ArgumentNullException("socket");
            }

            int maxServerID = servers.Count - 1;
            if (maxServerID < socket.OwnerID)
            {
                throw new InvalidOperationException(string.Format("Invalid socket server ID: {0}, maximum should be: {1}", socket.OwnerID, maxServerID));
            }

            IMemcachedServerNode serverNode = servers[socket.OwnerID];
            if (socket.Dead)
            {
                serverNode.RenewFailedSocket(socket);
            }
            else
            {
                serverNode.ReleaseSocket(socket);
            }
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            serverNodeMonitor.Stop();
            servers.ForEach(s => s.Dispose());
        }

        /// <summary>
        /// Initializes the server pool with the configured values
        /// </summary>
        private void InitPool()
        {
            servers = new List<IMemcachedServerNode>();
            int currentServerNumber = 0;

            foreach (KeyValuePair<IPAddress, int> serverPair in targetServers)
            {
                IPAddress address = serverPair.Key;
                int portNumber = serverPair.Value;

                MemcachedServerNode memcachedServerNode = new MemcachedServerNode(address, portNumber, socketType, protocol, maxSocketsPerServer, currentServerNumber);
                servers.Add(memcachedServerNode);
                ++currentServerNumber;
            }

            serverLocator = new ServerLocator(servers);
        }

        /// <summary>
        /// Configures this instance with the values in the given configuration.
        /// </summary>
        /// <param name="configuration">The configuration.</param>
        private void ConfigurePool(MemcachedClientConfiguration configuration)
        {
            MemcachedConfigurationParser parser = new MemcachedConfigurationParser(configuration);
            targetServers = parser.MemcachedServerList;
            socketType = SocketType.Stream;
            protocol = ProtocolType.Tcp;
            maxSocketsPerServer = parser.MaxSocketsPerServer;
        }
    }
}
